/*
    This file is part of QTau
    Copyright (C) 2013-2018  Tobias "Tomoko" Platen <tplaten@posteo.de>
    Copyright (C) 2013       digited       <https://github.com/digited>
    Copyright (C) 2010-2013  HAL@ShurabaP  <https://github.com/haruneko>

    QTau is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    SPDX-License-Identifier: GPL-3.0+
*/

#include "audio/outputbuffer.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <stdint.h>
#include <sys/time.h>

#define __devloglevel__ 4

#define debugperf 0
#define debugperf2 0

uint32_t getTime() {
  struct timeval tv;
  gettimeofday(&tv, NULL);
  uint32_t ret = static_cast<uint32_t>(tv.tv_usec / 1000 + tv.tv_sec * 1000);
  return ret;
}

OutputBuffer::OutputBuffer(JackAudio* jack) {
  _buffersize = 64 * 1024;
  _jackSamplerate = jack->getSamplerate();
  _ringbuffer = jack_ringbuffer_create(_buffersize);
  _sndfile = nullptr;
  _datacount = 0;
  start();
}

void OutputBuffer::scheduleSynth(ISynth* synth) {
  if (_scheduledSynth) return;
  _datacount = 0;
  _runtime = 0;
  jack_ringbuffer_reset(_ringbuffer);
  open("/tmp/test.wav", 44100);
  _scheduledSynth = synth;
}

void OutputBuffer::run() {
  int data_size = 1024 * 2;
  float* data = new float[data_size];
  uint32_t lasttime = getTime();
  while (1) {
    if (_scheduledSynth) {
      int end = _scheduledSynth->readData(data, data_size);
      if (end == 0) {
        _scheduledSynth->readData(nullptr, 0);
        _scheduledSynth = nullptr;
        close();
        continue;
      }

      int writespace = jack_ringbuffer_write_space(_ringbuffer);
      int size2 = data_size * sizeof(float);

      int sleepcount = 0;
      while (writespace < 2 * size2) {
        if (!_playbackIsStable) emit startPlayback();
        _playbackIsStable = true;
        usleep(1000);
        sleepcount++;
        writespace = jack_ringbuffer_write_space(_ringbuffer);
      }

      uint32_t currenttime = getTime();
      uint32_t timedelta = currenttime - lasttime;
      lasttime = currenttime;
      if (debugperf)
        DEVLOG_DEBUG("sleepcount " + STR(sleepcount) + " writespace " +
                     STR(writespace) + " timedelta " + STR(timedelta));
      _runtime += timedelta;
      uint32_t timedelta_max = (int)(data_size * 1000 / 44100);
      if (debugperf2)
        if (timedelta > timedelta_max)
          DEVLOG_DEBUG("timedelta_max " + STR(timedelta_max) + " timedelta " +
                       STR(timedelta - sleepcount));

      jack_ringbuffer_write(_ringbuffer, (char*)data, size2);
      sf_write_float(_sndfile, data, data_size);
    } else {
      usleep(20000);  // nothing to do
    }
  }
}

// on synth start
void OutputBuffer::open(QString fileName, int samplerate) {
  if (_sndfile) sf_close(_sndfile);
  SF_INFO info;
  memset(&info, 0, sizeof(info));
  info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
  info.samplerate = samplerate;
  info.channels = 1;
  _sndfile = sf_open(fileName.toUtf8().data(), SFM_WRITE, &info);
  if (_jackSamplerate != samplerate) {
    DEVLOG_ERROR("secret rabbit code is not implemented yet\n");
  }
}

void OutputBuffer::openReadFile(QString fileName) {
  SF_INFO info;
  memset(&info, 0, sizeof(info));
  _sndfile = sf_open(fileName.toUtf8().data(), SFM_READ, &info);
  _offline = true;
}

// on synth end
void OutputBuffer::close() {
  if (_sndfile) sf_close(_sndfile);
  _sndfile = nullptr;
  jack_ringbuffer_reset(_ringbuffer);
  _datacount = 0;
}
// other thread -- copy engine
void OutputBuffer::readData(float* data, int size) {
  if (_offline && _sndfile) {
    int count = sf_readf_float(_sndfile, data, size);
    if (count == 0) {
      DEVLOG_DEBUG("offline mode: stop playback");
      emit stopPlayback();
      close();
    }
    return;
  }

  // if(!_playbackIsStable) return; //do nothing
  unsigned int data_size = (unsigned int)size * sizeof(float);
  memset(data, 0, data_size);

  unsigned int readspace = jack_ringbuffer_read_space(_ringbuffer);
  float filled = readspace * 1.0 / _buffersize;
  if (filled < 0.5 && debugperf) DEVLOG_DEBUG("reading data " + STR(filled));

  if (!_playbackIsStable) return;

  if (readspace >= data_size) {
    jack_ringbuffer_read(_ringbuffer, (char*)data, data_size);
  } else {
    if (_scheduledSynth) {
      if (debugperf) DEVLOG_DEBUG("XRUN - cannot read from buffer");
      _xruncount++;
    } else {
      if (_playbackIsStable) {
        DEVLOG_DEBUG("end reached xruncount=" + STR(_xruncount) +
                     " cputime=" + STR(_runtime));
        stopPlayback();
        _playbackIsStable = false;
      }
    }
  }
}

void OutputBuffer::reset() {
  jack_ringbuffer_reset(_ringbuffer);
  _datacount = 0;
}
